﻿using Devil;
using LitJson;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;

namespace GameToolkit.Editor
{

    using Package = UnityEditor.PackageManager.PackageInfo;

    [System.Serializable, ProjectConfig("Game Toolkit/Create Package Wizzard")]
    public class CreatePackageWizzard : ProjectConfiguration.Configurable
    {
        readonly static Regex PACK_NAME_REGEX = new Regex(@"^com(\.[a-zA-Z-_0-9]+)+$");
        readonly static Regex PACK_VERSION_REGEX = new Regex(@"^\d+\.\d+\.\d+$");
        readonly static Regex ASSEMBLY_NAME_REGEX = new Regex(@"^[a-zA-Z_0-9\.]+$");


        public string m_PackageFolder;

        public string m_Namespace;

        public string m_PackageName;

        public string m_PackageVersion;

        public string m_DisplayName;

        public string m_Description;

        public string m_AssemblyName;

        public List<string> m_DependencyPacks = new List<string>();

        public List<AssemblyDefinitionAsset> m_DependencyAssmblies = new List<AssemblyDefinitionAsset>();

        public bool m_CreateEditor;

        ReorderableList mDependencyPacksLst;
        ReorderableList mDependencyAsmdefs;

        string GetUnityVersion()
        {
#if UNITY_2021_3_OR_NEWER
            return "2021.3";
#elif UNITY_2021_1_OR_NEWER
            return "2021.1";
#elif UNITY_2020_1_OR_NEWER
            return "2020.1";
#elif UNITY_2019_1_OR_NEWER
            return "2019.1";
#else
            return "2021.3";
#endif
        }

        public override void OnEnableGUI()
        {
            base.OnEnableGUI();
            mDependencyPacksLst = new ReorderableList(m_DependencyPacks, typeof(string), true, true, true, true);
            mDependencyPacksLst.drawHeaderCallback = DrawDependencyLstHeaderGUI;
            mDependencyPacksLst.drawElementCallback = DrawDependencyElementGUI;
            mDependencyPacksLst.onAddDropdownCallback = DropdownPackageGUI;

            mDependencyAsmdefs = new ReorderableList(m_DependencyAssmblies, typeof(AssemblyDefinitionAsset), true, true, true, true) ;
            mDependencyAsmdefs.drawHeaderCallback = DrawAsmdefHeaderGUI;
            mDependencyAsmdefs.drawElementCallback = DrawAsmdefElementGUI;
            mDependencyAsmdefs.onAddDropdownCallback = DropdownAsmdefGUI;
        }

        private void DropdownAsmdefGUI(Rect buttonRect, ReorderableList list)
        {
            m_DependencyAssmblies.Add(null);
        }

        private void DrawAsmdefElementGUI(Rect rect, int index, bool isActive, bool isFocused)
        {
            var asmdef = m_DependencyAssmblies[index];
            rect = EditorGUI.PrefixLabel(rect, new GUIContent(asmdef == null ? "missing" : asmdef.name));
            var asmdef1 = EditorGUI.ObjectField(rect, asmdef, typeof(AssemblyDefinitionAsset), false) as AssemblyDefinitionAsset;
            if (asmdef1 != asmdef)
            {
                m_DependencyAssmblies[index] = asmdef1;
                SetDirty();
            }
        }

        private void DrawAsmdefHeaderGUI(Rect rect)
        {
            EditorGUI.LabelField(rect, "Dependency Assemblies");
        }

        private void DropdownPackageGUI(Rect buttonRect, ReorderableList list)
        {
            var packs = Package.GetAllRegisteredPackages();
            var menu = new GenericMenu();
            GenericMenu.MenuFunction2 addPackAction = (dt) =>
            {
                if (dt is Package pack && !m_DependencyPacks.Contains(pack.name))
                {
                    m_DependencyPacks.Add(pack.name);
                    SetDirty();
                }
            };
            foreach (var pack in packs)
            {
                var i1 = pack.name.IndexOf('.');
                var i2 = pack.name.LastIndexOf('.');
                var com = i1 > 0 && i2 > i1 ? pack.name.Substring(i1 + 1, i2 - i1 - 1).Replace('.', '/'): "default";
                var content = new GUIContent($"{com}/{pack.name}", pack.displayName);
                if (m_DependencyPacks.Contains(pack.name))
                    menu.AddDisabledItem(content, true);
                else
                    menu.AddItem(content, false, addPackAction, pack);
            }
            menu.DropDown(buttonRect);
        }

        private void DrawDependencyElementGUI(Rect rect, int index, bool isActive, bool isFocused)
        {
            EditorGUI.LabelField(rect, m_DependencyPacks[index]);
        }

        private void DrawDependencyLstHeaderGUI(Rect rect)
        {
            EditorGUI.LabelField(rect, "Dependency Packages");
        }

        public override void OnGUI()
        {
            EditorGUI.BeginChangeCheck();
            EditorGUILayout.BeginHorizontal();
            m_PackageFolder = EditorGUILayout.TextField("Package Folder", m_PackageFolder);
            if (GUILayout.Button("Select", GUILayout.Width(80)))
            {
                var path = EditorUtility.OpenFolderPanel("Package Folder", m_PackageFolder, "");
                if (!string.IsNullOrEmpty(path) && path != m_PackageFolder)
                {
                    m_PackageFolder = path;
                    GUI.changed = true;
                }
            }
            EditorGUILayout.EndHorizontal();

            m_PackageName = EditorGUILayout.TextField("Package Name", m_PackageName);
            m_PackageVersion = string.IsNullOrEmpty(m_PackageVersion) ? "1.0.0" : m_PackageVersion;
            m_DisplayName = EditorGUILayout.TextField("Display Name", m_DisplayName);
            m_AssemblyName = EditorGUILayout.TextField("Assembly Name", m_AssemblyName);
            m_Namespace = EditorGUILayout.TextField("Root Namespace", m_Namespace);
            m_CreateEditor = EditorGUILayout.Toggle("Create Editor", m_CreateEditor);

            mDependencyPacksLst.DoLayoutList();
            mDependencyAsmdefs.DoLayoutList();

            EditorGUILayout.PrefixLabel("Description");
            m_Description = EditorGUILayout.TextArea(m_Description, GUILayout.MinHeight(80));

            EditorGUILayout.BeginHorizontal(GUILayout.MaxWidth(400));
            var create = GUILayout.Button("Create Package");
            var cleancreate = GUILayout.Button("Create Package & Clean");
            EditorGUILayout.EndHorizontal();

            if (create || cleancreate)
            {
                var error = ParallelUtils.GetBuilder();
                if (string.IsNullOrEmpty(m_PackageFolder) || !NativeFileSystem.HasFolder(m_PackageFolder))
                {
                    error.AppendLine($"The folder ({m_PackageFolder}) don't exist");
                }
                if(!string.IsNullOrEmpty(m_Namespace) && !ASSEMBLY_NAME_REGEX.IsMatch(m_Namespace))
                {
                    error.AppendLine($"Invalid Namespace Name: {m_Namespace}");
                }
                if(string.IsNullOrEmpty(m_PackageName) || !PACK_NAME_REGEX.IsMatch(m_PackageName))
                {
                    error.AppendLine($"Invalid Package Name: {m_PackageName}");
                }
                if(string.IsNullOrEmpty(m_PackageVersion) || !PACK_VERSION_REGEX.IsMatch(m_PackageVersion))
                {
                    error.AppendLine($"Invalid Package Version: {m_PackageVersion}");
                }
                if(string.IsNullOrEmpty(m_DisplayName))
                {
                    error.AppendLine("Empty Display Name");
                }
                if(string.IsNullOrEmpty(m_AssemblyName) || !ASSEMBLY_NAME_REGEX.IsMatch(m_AssemblyName))
                {
                    error.AppendLine($"Invalid Assembly Name: {m_AssemblyName}");
                }
                if(error.Length > 0)
                {
                    EditorUtility.DisplayDialog("Error", ParallelUtils.ReturnRelease(error), "OK");
                }
                else
                {
                    CreatePackage();
                    if (cleancreate)
                    {
                        m_PackageName = "";
                        m_DisplayName = "";
                        m_AssemblyName = "";
                        m_Namespace = "";
                        m_CreateEditor = false;
                        m_DependencyPacks.Clear();
                        m_DependencyAssmblies.Clear();
                        GUI.changed = true;
                    }
                }

            }

            var dirty = EditorGUI.EndChangeCheck();
            if (dirty)
                SetDirty();
        }

        void CreatePackage()
        {
            var folder = NativeFileSystem.Combine(m_PackageFolder, m_DisplayName.Replace(" ", ""));
            NativeFileSystem.EnsureDirectory(folder);
            var packInfo = new JsonData();
            packInfo["name"] = m_PackageName;
            packInfo["version"] = m_PackageVersion;// "1.0.0";
            packInfo["displayName"] = m_DisplayName;
            packInfo["description"] = string.IsNullOrEmpty(m_Description) ? m_DisplayName : m_Description;
            packInfo["unity"] = GetUnityVersion();
            //packInfo["documentationUrl"] = "";
            //packInfo["changelogUrl"] = "";
            //packInfo["licensesUrl"] = "";
            var dependencies = new JsonData(JsonType.Object);
            packInfo["dependencies"] = dependencies;
            packInfo["keywords"] = new JsonData(JsonType.Array);
            foreach (var pack in m_DependencyPacks)
            {
                var info = Package.FindForAssetPath($"Packages/{pack}/package.json");
                if (info == null)
                {
                    Debug.LogError($"Don't find package: {pack}");
                }
                else
                {
                    dependencies[info.name] = info.version;
                }
            }
            foreach (var asmdef in m_DependencyAssmblies)
            {
                if (asmdef == null)
                    continue;
                var path = AssetDatabase.GetAssetPath(asmdef);
                if (string.IsNullOrEmpty(path))
                    continue;
                var pack = Package.FindForAssetPath(path);
                if (pack != null && !m_DependencyPacks.Contains(pack.name))
                    dependencies[pack.name] = pack.version;
            }
            NativeFileSystem.WriteAllText(NativeFileSystem.Combine(folder, "package.json"), packInfo.ToFormatJson(), Encoding.UTF8);
            NativeFileSystem.EnsureDirectory(NativeFileSystem.Combine(folder, "Runtime"));
            // assembly info
            var buffer = ParallelUtils.GetBuilder();
            buffer.AppendLine("using System.Runtime.CompilerServices;").NextLine();
            if (m_CreateEditor)
            {
                buffer.AppendLine($"[assembly: InternalsVisibleTo(\"{m_AssemblyName}.Editor\")]");
            }
            NativeFileSystem.WriteAllText(NativeFileSystem.Combine(folder, "Runtime/AssemblyInfo.cs"), buffer.ToString(), Encoding.UTF8);
            var rootNamespace = m_Namespace;
            if(string.IsNullOrEmpty(rootNamespace))
            {
                rootNamespace = m_AssemblyName;
            }
            // assembly define
            var asm = $"{m_AssemblyName}.Runtime";
            var asmInfo = GetAsmDef(rootNamespace, asm);
            var refs = asmInfo["references"];
            foreach (var asmdef in m_DependencyAssmblies)
            {
                if (asmdef != null)
                    refs.Add(asmdef.name);
            }
            NativeFileSystem.WriteAllText(NativeFileSystem.Combine(folder, $"Runtime/{asm}.asmdef"), asmInfo.ToFormatJson(), Encoding.UTF8);

            if (m_CreateEditor)
            {
                NativeFileSystem.EnsureDirectory(NativeFileSystem.Combine(folder, "Editor"));
                NativeFileSystem.WriteAllText(NativeFileSystem.Combine(folder, "Editor/AssemblyInfo.cs"), " ", Encoding.UTF8);
                asm = $"{m_AssemblyName}.Editor";
                asmInfo = GetAsmDef($"{rootNamespace}.Edit", asm);
                asmInfo["references"].Add($"{m_AssemblyName}.Runtime");
                asmInfo["includePlatforms"].Add("Editor");
                NativeFileSystem.WriteAllText(NativeFileSystem.Combine(folder, $"Editor/{asm}.asmdef"), asmInfo.ToFormatJson(), Encoding.UTF8);
            }
            EditorUtility.DisplayDialog("New Package", $"Create package \"{m_PackageName}\" @{folder}", "OK");
        }

        JsonData GetAsmDef(string rootNamespace, string asmName)
        {
            var isEditor = asmName.EndsWith(".Editor");
            var asmInfo = new JsonData();
            asmInfo["name"] = asmName;
            asmInfo["rootNamespace"] = rootNamespace;
            asmInfo["references"] = new JsonData(JsonType.Array);
            asmInfo["includePlatforms"] = new JsonData(JsonType.Array);
            asmInfo["excludePlatforms"] = new JsonData(JsonType.Array);
            asmInfo["allowUnsafeCode"] = false;
            asmInfo["overrideReferences"] = false;
            asmInfo["precompiledReferences"] = new JsonData(JsonType.Array);
            asmInfo["autoReferenced"] = true;
            asmInfo["defineConstraints"] = new JsonData(JsonType.Array);
            asmInfo["versionDefines"] = new JsonData(JsonType.Array);
            asmInfo["noEngineReferences"] = false;
            if (isEditor)
                asmInfo["includePlatforms"].Add("Editor");
            return asmInfo;
        }
    }
}